Skip to content

(0.4.0) Add radiation as a top-level EarthSystemModel component#200

Open
glwagner wants to merge 16 commits intomainfrom
glw/radiation
Open

(0.4.0) Add radiation as a top-level EarthSystemModel component#200
glwagner wants to merge 16 commits intomainfrom
glw/radiation

Conversation

@glwagner
Copy link
Copy Markdown
Member

@glwagner glwagner commented Apr 30, 2026

Summary

Closes #30. Wholesale refactor that promotes radiation from a ComponentInterfaces field into its own top-level EarthSystemModel component, mirroring the structure of #149 (land).

  • New src/Radiations/ module with PrescribedRadiation, SurfaceRadiationProperties, InterfaceRadiationFlux, the moved LatitudeDependentAlbedo / TabulatedAlbedo modules, and the radiation kernels.
  • EarthSystemModel struct: type-param order {R, A, L, I, O, F, C, Arch}; radiation field placed above atmosphere ("from the sun"). Default radiation = nothing ⇒ radiatively decoupled (no upwelling LW, no SW/LW absorption).
  • StateExchanger gains a radiation slot. ComponentInterfaces constructor takes radiation as a kwarg used only to construct the radiation exchanger.
  • Kernel split: _assemble_net_ocean_fluxes! and _assemble_net_sea_ice_fluxes! now do only turbulent + sea-ice contributions. New apply_air_sea_radiative_fluxes! and apply_air_sea_ice_radiative_fluxes! (in Radiations/) add radiative contributions on top, dispatch wholesale on coupled_model.radiation type, and write to per-surface InterfaceRadiationFlux diagnostics.
  • SkinTemperature consumes radiation via the new air_sea_interface_radiation_state(rk, ex_state, i, j, k, grid, time) getter (and sea-ice variant). When radiation is off the getter returns zeros, so SkinTemperature degrades cleanly to a turbulent-only flux balance.
  • PrescribedAtmosphere loses downwelling_radiation; TwoBandDownwellingRadiation is removed entirely (committing to two-band).
  • New data-wrangling constructors: JRA55PrescribedRadiation, ECCOPrescribedRadiation, OSPapaPrescribedRadiation.
  • All tests, examples, experiments, docs, and the Reactant ext updated.

Migration notes (breaking changes for v0.3)

  • Radiation() is gone. To enable radiation, build a PrescribedRadiation (or one of the data-wrangling helpers) and pass it via radiation = ... to OceanOnlyModel / OceanSeaIceModel / EarthSystemModel.
  • radiation = nothing (the new default) means no radiation at all — no incoming SW/LW absorption and no ϵσT⁴ surface emission. Existing JRA55-driven simulations need to construct JRA55PrescribedRadiation(arch; backend, ...) explicitly.
  • atmosphere.downwelling_radiation no longer exists. Read from radiation.downwelling_shortwave / radiation.downwelling_longwave instead.
  • Surface radiative properties are now bundled per-surface: SurfaceRadiationProperties(albedo, emissivity). Pass them to constructors as ocean_surface = SurfaceRadiationProperties(0.05, 0.97) etc.

Test plan

  • New test_radiations.jl covers PrescribedRadiation construction (FTS form + grid-only form), time_step!, surface-property filtering, pairing with OceanOnlyModel (verifies interface_fluxes allocation), and JRA55PrescribedRadiation loading. (Locally: 24/24 passing.)
  • test_ocean_only_model.jl — 5/5 passing locally with JRA55PrescribedRadiation.
  • test_surface_fluxes.jl — 24/24 passing locally; "no-radiation" cases use radiation=nothing directly.
  • test_diagnostics_2.jl — 44/44 passing locally with radiation=nothing.
  • CI to verify test_jra55, test_ocean_sea_ice_model, test_sea_ice_ocean_heat_fluxes, test_checkpointer, test_ecco_atmosphere, test_ospapa, test_reactant, test_speedy_coupling.

🤖 Generated with Claude Code

glwagner and others added 3 commits April 29, 2026 17:38
Wholesale refactor: radiation moves out of ComponentInterfaces and becomes
its own model component, mirroring the land PR (#149) shape.

- New src/Radiations/ module with PrescribedRadiation, SurfaceRadiationProperties,
  InterfaceRadiationFlux, the radiation kernels, and the moved albedo modules.
- EarthSystemModel struct: type-param order {R, A, L, I, O, F, C, Arch};
  radiation placed above atmosphere ("from the sun"). Default `radiation = nothing`
  means radiatively decoupled (no upwelling LW, no SW/LW absorption).
- StateExchanger gains a radiation slot; component_interfaces takes radiation
  as a kwarg used only to construct the radiation exchanger.
- Kernel split: _assemble_net_ocean_fluxes! and _assemble_net_sea_ice_fluxes!
  now do turbulent contributions only. New apply_air_sea_radiative_fluxes! and
  apply_air_sea_ice_radiative_fluxes! kernels (in Radiations/) add radiative
  contributions on top, dispatch on `coupled_model.radiation` type, and write
  to per-surface InterfaceRadiationFlux diagnostics.
- SkinTemperature consumes radiation via the new `air_sea_interface_radiation_state`
  (and sea-ice variant) getter, which returns zeros when radiation is off — so
  SkinTemperature degrades cleanly to a turbulent-only flux balance.
- PrescribedAtmosphere drops downwelling_radiation / TwoBandDownwellingRadiation;
  atmosphere exchanger state shrinks to (u, v, T, p, q, Jᶜ).
- New JRA55PrescribedRadiation, ECCOPrescribedRadiation, OSPapaPrescribedRadiation
  data-wrangling constructors.
- Reactant ext updated for the new type-param order.

Tests, examples, and docs migrations to follow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Replace Radiation(arch) with JRA55PrescribedRadiation(arch; backend) in
  test_ocean_only_model.jl, test_ocean_sea_ice_model.jl,
  test_sea_ice_ocean_heat_fluxes.jl, test_checkpointer.jl, test_reactant.jl.
- Replace 'no radiation' Radiation(emissivity=0, albedo=1) and Radiation()
  defaults with radiation=nothing in test_surface_fluxes.jl, test_diagnostics_2.jl,
  test_speedy_coupling.jl.
- Update test_ecco_atmosphere.jl and test_ospapa.jl to also build
  ECCOPrescribedRadiation / OSPapaPrescribedRadiation and assert against
  radiation.downwelling_shortwave / longwave.
- New test_radiations.jl covers PrescribedRadiation construction, time_step!,
  pairing with OceanOnlyModel (verifies interface_fluxes allocation), and
  JRA55PrescribedRadiation loading.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- examples/{idealized_single_column,global_climate,near_global_ocean,
  one_degree,single_column_os_papa,meridional_heat_transport_ecco,
  veros_ocean_forced,generate_surface_fluxes}.jl all use the new
  PrescribedRadiation / JRA55PrescribedRadiation / OSPapaPrescribedRadiation /
  ECCOPrescribedRadiation API.
- experiments/{arctic,one_degree,coupled,flux_climatology}.jl migrated.
- experiments/coupled passes a LatitudeDependentAlbedo through the new
  SurfaceRadiationProperties bundle.
- docs/src/earth_system_model.md doctest output reflects the new
  'radiation:' line in the EarthSystemModel show.
- Fix FreezingLimitedEarthSystemModel and Single*FreezingLimited type
  aliases to use the new {R, A, L, I, O, F, C, Arch} type-parameter order.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@glwagner glwagner changed the title (0.3.0) Add radiation as a top-level EarthSystemModel component (0.4.0) Add radiation as a top-level EarthSystemModel component Apr 30, 2026
Comment thread examples/global_climate_simulation.jl Outdated
Comment thread examples/idealized_single_column_simulation.jl Outdated
Comment thread examples/meridional_heat_transport_ecco.jl Outdated
Comment thread examples/global_climate_simulation.jl Outdated
Comment thread examples/one_degree_simulation.jl Outdated
Comment thread experiments/coupled_simulation/earth_system_coupled_simulation.jl
Comment thread src/Atmospheres/prescribed_atmosphere.jl
Comment thread src/DataWrangling/ECCO/ECCO_radiation.jl Outdated
Comment thread src/DataWrangling/OSPapa/OSPapa_prescribed_atmosphere.jl
Comment thread src/DataWrangling/OSPapa/OSPapa_prescribed_radiation.jl Outdated
Comment thread src/Radiations/air_sea_interface_radiation_state.jl Outdated
Comment thread src/Radiations/air_sea_interface_radiation_state.jl Outdated
glwagner and others added 2 commits April 30, 2026 08:23
- SurfaceRadiationProperties gains a kwarg constructor with a default
  emissivity (0.97), letting users write
  `SurfaceRadiationProperties(albedo = 0.1)` to override only the albedo.
- New top-level `default_stefan_boltzmann_constant = 5.670374419e-8`
  constant in Radiations; all PrescribedRadiation / data-wrangling
  constructors now default to it.
- Reorder positional arguments to match struct (top→bottom) convention:
  - `EarthSystemModel(radiation, atmosphere, land, sea_ice, ocean; ...)`
  - `OceanSeaIceModel(sea_ice, ocean; ...)`
  All call sites in src, tests, examples, experiments, and docs flipped.
- Fix indent in ECCOPrescribedRadiation docstring.
- Apply review-suggested formatting to air_sea_(ice_)interface_radiation_state.
- Update docstring in earth_system_model.jl to reflect the new positional-arg
  convention.
- one_degree_simulation.jl now demonstrates a custom ocean albedo via
  `SurfaceRadiationProperties(albedo = LatitudeDependentAlbedo())`.
- Fix Speedy comment in global_climate_simulation.jl: NumericalEarth still
  computes turbulent fluxes; only the radiation path is unwired against
  Speedy upwelling-LW.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@glwagner
Copy link
Copy Markdown
Member Author

Addressed the comments in commit 83a2151:

  • SurfaceRadiationProperties kwarg constructor with default emissivity — added SurfaceRadiationProperties(; albedo, emissivity = 0.97). The idealized single-column example now uses SurfaceRadiationProperties(albedo = 0.1), and one_degree_simulation.jl demonstrates SurfaceRadiationProperties(albedo = LatitudeDependentAlbedo()) to show that the JRA55 constructor accepts changed surface properties.
  • Argument order conventionEarthSystemModel(radiation, atmosphere, land, sea_ice, ocean; ...) and OceanSeaIceModel(sea_ice, ocean; ...) (top→bottom struct order). Pass nothing for absent components. All call sites in src, tests, examples, experiments, and docs updated.
  • Default stefan_boltzmann_constant — added Radiations.default_stefan_boltzmann_constant = 5.670374419e-8 (CODATA 2018) and used as the default everywhere. Per-call-site override still works.
  • ECCO indent — fixed.
  • Speedy comment in global_climate_simulation.jl — corrected. NumericalEarth still computes turbulent fluxes; only the radiation path is unwired against Speedy upwelling-LW, so radiation = nothing skips just that.

Skipped (per your scope-creep notes): freshwater_fluxprecipitation rename (#36 follow-up), _fts_field_time_series rename (separate PR).

The two @inline ... formatting suggestions you applied directly to the branch (53aa209, bd25141) are now incorporated via the rebase.

glwagner and others added 3 commits April 30, 2026 08:44
The new `EarthSystemModel(; radiation, atmosphere, land, sea_ice, ocean, kw...)`
form is equivalent to the positional one but skips the awkward `nothing`
padding for absent components:

  EarthSystemModel(; atmosphere, ocean)                     # ocean + atmosphere
  EarthSystemModel(; atmosphere, sea_ice, ocean, radiation) # full coupled

All previously-padded call sites in test_speedy_coupling.jl,
examples/global_climate_simulation.jl, and docs/src/developers/slab_ocean.jl
switch to the kwarg form for readability.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
test_ocean_sea_ice_model.jl line 82 still had the old
OceanSeaIceModel(ocean_with_land, sea_ice_with_land; ...) order. The
earlier sed only matched literal 'ocean,' so it skipped this variant.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The test/Manifest.toml was committed by mistake in b9260d8. Each Julia
version generates its own resolved manifest from test/Project.toml, so
committing one causes 'manifest differs' warnings (and ultimately
resolution failures) on CI runs that use a different Julia version
than the one the manifest was generated against.

Add test/Manifest.toml to .gitignore alongside the existing root and
docs entries.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
glwagner and others added 2 commits April 30, 2026 12:56
GPU CI hit a NetCDF: HDF error opening JRA55/RYF.rlds.1990_1991.nc — a
corrupted cached download. The existing init guard only constructs a
JRA55PrescribedAtmosphere, which never touches the radiation files
(rlds/rsds). Now that those files are loaded at test time via
JRA55PrescribedRadiation, a bad cached file isn't caught until much later.

Construct a JRA55PrescribedRadiation in the same try/catch so the
NumericalEarthArtifacts fallback path triggers for radiation downloads
too, mirroring the atmosphere variables.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread src/Radiations/apply_air_sea_ice_radiative_fluxes.jl
Comment thread src/EarthSystemModels/InterfaceComputations/component_interfaces.jl
Comment thread src/EarthSystemModels/earth_system_model.jl
@ewquon
Copy link
Copy Markdown
Collaborator

ewquon commented May 1, 2026

I expect that adding something like ERA5PrescribedRadiation should be a new Issue?

surface_layer_height = surface_layer_height(coupled_model.atmosphere),
gravitational_acceleration = coupled_model.interfaces.properties.gravitational_acceleration)

# Radiation state for the interface solve (used by SkinTemperature).
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General question: Is the same SkinTemperature used over the ocean and land? And are we missing something like src/EarthSystemModels/InterfaceComputations/atmosphere_land.jl following #149?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently SkinTemperature can be used by sea ice or ocean... we don't have a prognostic land yet for which this could be used, but that would definitely be the plan.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re atmosphere_land.jl this needs to be added in order to enable atmosphere simulations with prescribed or prognostic land... something that we cannot do yet, because the land component is very minimal placeholder right now. So yeah, more work needed.

end

ZeroFluxes() = ZeroFluxes(ntuple(_ -> ZeroField(), 14)...)
ZeroFluxes() = ZeroFluxes(ntuple(_ -> ZeroField(), 11)...)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is something like

Nfields = fieldcount(ZeroFluxes)
ZeroFluxes() = ZeroFluxes(ntuple(_ -> ZeroField(), Nfields)...)

bad form?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That won't work as-is because Nfields is global, which would cause a type inference failure. But this might work:

ZeroFluxes() = ZeroFluxes(ntuple(_ -> ZeroField(), fieldcount(ZeroFluxes))...)

or adding const in front of Nfields

Comment thread src/Oceans/assemble_net_ocean_fluxes.jl
Comment on lines +23 to +24
@inline net_absorbed_interface_radiation(ℐꜜˢʷ, ℐꜜˡʷ, α, ϵ) = - (1 - α) * ℐꜜˢʷ - ϵ * ℐꜜˡʷ
@inline emitted_longwave_radiation(T, σ, ϵ) = σ * ϵ * T^4
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these ever called? It seems like the same code is repeated throughout:

  • src/Radiations/apply_air_sea_radiative_fluxes.jl
  • src/Radiations/apply_air_sea_ice_radiative_fluxes.jl
  • src/Radiations/radiation_kernels.jl

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm that is concerning. let me look at that

Copy link
Copy Markdown
Collaborator

@ewquon ewquon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a nice bit of surgery @glwagner. The implemented code reflect the described development, as far as I can tell. My main concern is the dead/repeated code in radiation_kernels.jl 6fd48b4#r3174870639

glwagner and others added 3 commits May 1, 2026 14:01
…es.jl

Co-authored-by: Eliot Quon <eliot@aeolus.earth>
Co-authored-by: Eliot Quon <eliot@aeolus.earth>
Co-authored-by: Eliot Quon <eliot@aeolus.earth>
@glwagner
Copy link
Copy Markdown
Member Author

glwagner commented May 1, 2026

I expect that adding something like ERA5PrescribedRadiation should be a new Issue?

Yes! And we can either convert one of the existing examples or make a new example that is forced by ERA5 rather than JRA55. I think this is important.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add radiation as a top-level EarthSystemModel component

2 participants